<!DOCTYPE HTML>
<html>
<head><meta name="generator" content="Hexo 3.9.0">
  <meta charset="utf-8">
  
  <title>Java虚拟机 › 但行好事，莫问前程</title>
  <meta name="author" content="fengbo">
  
  <meta name="description" content="不想当架构师的程序员不是好厨师 ，不认为PHP是世界上最好的编程语言的Python开发者不是一个好的Javaer。">
  
  
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

  <meta property="og:title" content="Java虚拟机">
  <meta property="og:site_name" content="但行好事，莫问前程">

  
    <meta property="og:image" content="undefined">
  

  <link href="/favicon.png" rel="icon">
  <link rel="alternate" href="/atom.xml" title="但行好事，莫问前程" type="application/atom+xml">
  <link rel="stylesheet" href="/css/style.css" media="screen" type="text/css">
  <!--[if lt IE 9]><script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
  

</head>
</html>

<body>
  <header id="header"><div class="meta inner">
  <h1><a href="/">但行好事，莫问前程</a></h1>
  <h2><a href="/">CV工程师，了解面向Google编程的基本原理，有三年使用Google经验，在通往程序员的道路上艰难的爬行着。</a></h2>
  <nav id="main-nav">
    <ul>
      
      <li><a href="/">Home</a></li>
      
      <li><a href="/archives">Archives</a></li>
      
      <li><a href="/atom.xml">RSS</a></li>
      
    </ul>
    <div class="clearfix"></div>
  </nav>
</div>
<div class="clearfix"></div>
</header>
  <div id="content" class="inner">
    <div id="main-col" class="alignleft"><div id="wrapper"><article class="post">
  <div class="post-content">
    <header>
      
  
    <h1 class="title">Java虚拟机</h1>
  

      
        <time datetime="2017-09-30T00:58:38.000Z">2017-09-30</time>
      
    </header>
    <div class="entry">
      
        <h2 id="内存模型相关概念"><a href="#内存模型相关概念" class="headerlink" title="内存模型相关概念"></a>内存模型相关概念</h2><h3 id="缓存一致性问题"><a href="#缓存一致性问题" class="headerlink" title="缓存一致性问题"></a>缓存一致性问题</h3><p>为了弥补处理器和内存运算速度的巨大差异，现代计算机都会加入一层读写速度接近处理器运算速度的高速缓存。将运算需要使用到的数据复制到缓存中，让运算能快速进行，当运算结束后再从缓存同步会内存中。所以在多处理器系统中，因为每个处理器都有自己的高速缓存，而它们又共享同一主存。当它们的运算任务都涉及同一块主内存区域是，可能导致各自缓存数据不一致。<br><a id="more"></a></p>
<h3 id="Java内存分析工具使用"><a href="#Java内存分析工具使用" class="headerlink" title="Java内存分析工具使用"></a>Java内存分析工具使用</h3><ul>
<li>top -Hp [pid]：查看进程中的线程情况。</li>
<li>jps：虚机机进程信息查看。</li>
<li>jinfo [pid]：查看Java进程的配置信息。</li>
<li>jstat -gcutil [pid] 1000 1000：查看gc回收情况，每一秒查询一次查询1000次。</li>
<li>jmap -heap [pid]：查看堆内存占用情况。</li>
<li>jmap -histo [pid]：查看堆里面的对象信息。</li>
<li>jstack [pid]：查看线程栈内对象情况。</li>
</ul>
<h3 id="内存重排序"><a href="#内存重排序" class="headerlink" title="内存重排序"></a>内存重排序</h3><p>同时为了使得处理器内部的运算单元能尽量被充分利用，处理器可能会对输入代码进行乱序执行优化，处理器会在计算之后将乱序执行的结果重组，保证该结果与顺序执行的结果是一致的，但并不保证程序中各个语句计算的先后顺序与输入代码中的顺序一致，因此，如果存在一个计算任务依赖另外一个计算任务的<b>中间结果</b>，那么其顺序性并不能靠代码的先后顺序来保证。</p>
<h3 id="缓存一致性解决办法"><a href="#缓存一致性解决办法" class="headerlink" title="缓存一致性解决办法"></a>缓存一致性解决办法</h3><ol>
<li>通过在总线加锁的方式；<ul>
<li>加锁会阻塞其他CPU对该部件（如内存）的访问，从而使得只有一个CPU能够使用该变量的内存。</li>
<li>锁住总线期间，其他CPU无法访问内存，导致效率低下。</li>
</ul>
</li>
<li>通过缓存一致性协议。<ul>
<li>如MESI协议，该协议保证了每个缓存中使用的共享变量的副本是一致的。</li>
<li>即当CPU写数据时，如果发现操作的变量是共享变量，即在其他CPU中也存在该变量的副本，会发出信号通知其他CPU将该变量的缓存行置为无效状态，因此当其他CPU需要读取这个变量时，发现自己缓存中缓存该变量的缓存行是无效的，那么它就会从内存重新读取。</li>
</ul>
</li>
</ol>
<h2 id="Java内存模型"><a href="#Java内存模型" class="headerlink" title="Java内存模型"></a>Java内存模型</h2><p>Java内存模型规定了所以的变量都存储在<b>主内存</b>中(对应主内存)；同时每条线程还有自己的<b>工作内存</b>(对应高速缓存)，线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝，线程对变量的所有操作（读取、赋值等）都必须在工作内存中进行，而不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量，线程间变量值的传递均需要通过主内存来完成。</p>
<h3 id="先行发生原则"><a href="#先行发生原则" class="headerlink" title="先行发生原则"></a>先行发生原则</h3><p>如果操作A先行发生于操作B，其实就是说在发生操作B之前，操作A产生的影响能被操作B观察到，影响包括修改了内存中共享变量的值、发送了消息、调用了方法等。</p>
<h3 id="Java内存模型中天然的先行发生关系"><a href="#Java内存模型中天然的先行发生关系" class="headerlink" title="Java内存模型中天然的先行发生关系"></a>Java内存模型中天然的先行发生关系</h3><ol>
<li>程序次序规则：在一个线程内，按照程序代码顺序，书写在前面的操作先行发生于书写在后面的操作。</li>
<li>管程锁定规则：一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是同一个锁，而“后面”是指时间上的先后顺序。</li>
<li>volatile变量规则：对一个volatile变量的写操作先行发生于后面对这个变量的读操作，这里的“后面”同样是指时间上的先后顺序。</li>
<li>传递性：如果操作A先行发生于操作B，操作B先行发生于操作C，那就可以得出操作A先行发生于操作C的结论。</li>
<li>线程启动规则：Thread对象的start()方法先行发生于此线程的每一个动作。</li>
<li>线程终止规则：线程中的所有操作都先行发生于对此线程的终止检测.</li>
<li>线程中断规则：对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生，可以通过Thread.interrupted()方法检测到是否有中断发生。</li>
<li>对象终结规则：一个对象的初始化完成（构造函数执行结束）先行发生于它的finalize()方法的开始。</li>
</ol>
<h2 id="JVM内存划分"><a href="#JVM内存划分" class="headerlink" title="JVM内存划分"></a>JVM内存划分</h2><h3 id="程序计数器-–-对应操作系统里的TCB"><a href="#程序计数器-–-对应操作系统里的TCB" class="headerlink" title="程序计数器 – 对应操作系统里的TCB"></a>程序计数器 – 对应操作系统里的TCB</h3><ul>
<li>程序计数器可以看做是当前线程锁执行的字节码的行号指示器。即记录了Java虚拟机正在执行的字节码指令的地址。</li>
<li>在虚拟机概念模型中（仅是概念模型，各种虚拟机可能会通过一些更高效的方式去实现），字节码解释器就是通过改变计数器的值来选取下一条需要执行的字节码指令，分支、循环、跳转、异常处理、线程恢复等基础功能都需要这个计数器来完成。</li>
<li>为了线程切换后能恢复到正确的执行位置，每条线程都有一个独立的程序计数器，各个计数器之间互不影响，独立存储。</li>
<li>如果线程正在执行一个Java方法，这个计数器记录的是正在执行的虚拟机字节码指令的地址；如果正在执行的是Native方法，这个计数器为空。</li>
</ul>
<h3 id="虚拟机栈-–-在HotSpot虚拟机中虚拟机栈和本地方法栈合二为一"><a href="#虚拟机栈-–-在HotSpot虚拟机中虚拟机栈和本地方法栈合二为一" class="headerlink" title="虚拟机栈 – 在HotSpot虚拟机中虚拟机栈和本地方法栈合二为一"></a>虚拟机栈 – 在HotSpot虚拟机中虚拟机栈和本地方法栈合二为一</h3><p>每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。栈里存在一个或多个栈帧，<b>每个方法从调用开始执行到执行完成的过程，都对应一个栈帧在虚拟机栈里面从入栈到出栈的过程。</b></p>
<ul>
<li>局部变量表：用于存放方法参数和方法内部定义的局部变量，局部变量表中可以存储八种基本数据类型和对象的引用。</li>
<li>操作数栈：一个后入先出的栈。操作数栈的每一个元素都可以是任意的Java数据类型；当方法刚刚开始执行的时候，这个方法的操作数栈是空的，在方法的执行过程中会有各种字节码指令在操作数栈中写入和提取内容，也就是出栈和入栈操作。例如，进行算术运算需要使用操作数栈，同时调用其他方法也是通过使用操作数栈进行参数传递的。</li>
<li>动态链接：每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用，持有这个引用是为了支持方法调用过程中的动态链接。</li>
<li>方法返回地址：方法正常退出时，调用者的PC计数器的值可以作为返回地址，栈帧中很可能会保存这个计数器值。而方法异常退出时，返回地址要通过异常处理表来确定，栈帧一般不会保存这部分信息。<ul>
<li>方法退出的过程实际上等同于把当前栈帧出栈，因此退出时可能只需的操作有：恢复上层方法的局部变量表和操作数栈；把返回值（如果有）压入调用者栈帧的操作数栈中；调用PC计数器的值以指向方法调用指令后面的一条指令等。</li>
</ul>
</li>
<li>附加信息：虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧中，例如调试信息。</li>
</ul>
<h3 id="Java堆"><a href="#Java堆" class="headerlink" title="Java堆"></a>Java堆</h3><p>Java堆是Java虚拟机所管理的内存中最大的一块。它被所有的线程共享，在虚拟机启动时从创建。此内存的唯一目的就是存放对象实例。它也是垃圾收集器管理的主要区域，从内存回收的角度来看，由于现在的收集器基本都是使用分代收集算法，所以Java堆可以划分为新生代和老年代。其中新生代又分为Eden区和Survivor区。<b>从内存分配的角度看，线程共享的Java堆中可能划分出多个线程私有的分配缓冲区。</b></p>
<h3 id="方法区"><a href="#方法区" class="headerlink" title="方法区"></a>方法区</h3><p>方法区是JVM规范中的一个概念。它和堆一样是各个线程共享的内存区域，用于存储类信息、常量池、静态变量和JIT编译后的代码等数据。方法区逻辑上属于堆的一部分，但是为了与堆进行区分，通常又叫非堆。<a href="https://www.zhihu.com/question/49044988/answer/113961406" target="_blank" rel="noopener">其中HotSpot虚拟机使用永久代来实现方法区</a>，这样HotSpot的垃圾收集器可以想管理Java堆一样管理这部分内存。方法区的内存回收主要针对常量池和对类型的卸载。</p>
<ul>
<li>在Java6中，除了JIT编译生成的代码存放在本地内存的CodeCache区域，其他都存放在永久代；</li>
<li>在Java7中，Symbol的存储从PermGen移动到了Native Memory，并且把静态变量从instanceKlass末尾（位于PermGen内）移动到了java.lang.Class对象的末尾（位于普通Java heap内）；</li>
<li>在Java8中，永久代被彻底移除，取而代之的是另一块与堆不相连的本地内存——元空间（Metaspace）。</li>
</ul>
<h3 id="运行时常量池"><a href="#运行时常量池" class="headerlink" title="运行时常量池"></a>运行时常量池</h3><p>运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外，还有一项信息是常量池，用于存放编译期生成的各种字面量和符号引用，这部分内容在类加载后进入方法区的运行时常量池中存放。一般来说，翻译除了的直接引用也存储在运行时常量池中。</p>
<ul>
<li>字面量：比较接近于Java语言层面的常量的概念，如文本字符串、声明为final的常量值等。</li>
<li>符号引用：属于编译原理方面的概念，包括：1.类和接口的全限定名；2.字段的名称和描述符；3.方法的名称和描述符。</li>
</ul>
<h2 id="JVM垃圾收集"><a href="#JVM垃圾收集" class="headerlink" title="JVM垃圾收集"></a>JVM垃圾收集</h2><h3 id="判断对象是否死亡"><a href="#判断对象是否死亡" class="headerlink" title="判断对象是否死亡"></a>判断对象是否死亡</h3><ul>
<li>引用计数法：给对象中添加一个引用计数器，每当有一个地方引用它时，计数器值就加1；当引用失效时，计数器值就减1；任何时刻计数器为0的对象就是不可能再被使用的。<ul>
<li>相互引用时无法回收垃圾，如对象objA和objB都有字段instance，赋值令objA.instance=objB及objB.instance=objA，永远不会回收这两个对象，虽然这两个对象已经无用了。</li>
</ul>
</li>
<li>可达性分析：通过一系列称为GC Roots的对象作为起始点，从这些节点向下搜索，搜索走过的路径成为引用链，当一个对象到GC Roots没有任何引用链时，判断该对象可回收。<ul>
<li>在Java语言中，可作为GC Roots的对象有虚拟机栈（栈帧中的本地变量表）中引用的对象、方法区中类静态属性引用的对象（即static成员变量引用的对象）、方法区中常量引用的对象、本地方法栈中JNI（即一般说的Native方法）引用的对象。</li>
</ul>
</li>
</ul>
<h3 id="引用的四种方式"><a href="#引用的四种方式" class="headerlink" title="引用的四种方式"></a>引用的四种方式</h3><ul>
<li>强引用是指在程序代码之中普遍存在的，类似Object obj = new Object()这类的引用，只要强引用还存在，垃圾收集器永远不会回收掉被引用的对象。</li>
<li>软引用是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象，在系统将要发生内存溢出异常之前，将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存，才会抛出内存溢出异常。在JDK1.2之后，提供了SoftReference类来实现软引用。</li>
<li>弱引用也是用来描述非必需对象的，但是它的强度比软引用更弱一些，被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时，无论当前内存是否足够，都会回收掉只被弱引用关联的对象。在JDK1.2之后，提供了WeakReference类来实现弱引用。 </li>
<li>虚引用也称为幽灵引用或者幻影引用，它是最弱的一种引用关系。一个对象是否有虚引用的存在，完全不会对其生存时间构成影响，也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2之后，提供了PhantomReference类来实现虚引用。</li>
</ul>
<h3 id="分代垃圾收集"><a href="#分代垃圾收集" class="headerlink" title="分代垃圾收集"></a>分代垃圾收集</h3><p>分代垃圾收集算法基于以下两个观察事实（弱分代假设），如果不符合该假设，分代垃圾收集只会增加更多的开销，不过在实践中这样的引用很少。</p>
<ol>
<li>大多数分配对象的存活时间很短。</li>
<li>存活时间久的对象很少引用存活时间短的对象。 </li>
</ol>
<h3 id="Minor-GC"><a href="#Minor-GC" class="headerlink" title="Minor GC"></a>Minor GC</h3><ul>
<li>在进行新生代垃圾收集时垃圾收集器不需要扫描整个（可能比新生代更大）老年代就能识别新生代中的存活对象，从而缩短Minor GC时间。HotSpot VM的垃圾收集器使用<b>卡表</b>的数据结构来达到这个目的。老年代以512字节为快划分为若干张卡。卡表是个单字节数组，每个数组元素对应一张卡。每次老年代对象中某个引用新生代的字段发生变化时，HotSpot VM就必须将该卡所对应的卡表元素设置为适当的值，从而将该引用字段所在的卡标记为脏。在Minor GC过程中，垃圾收集器只会在脏卡中扫描查找老年代-新生代引用。</li>
<li>HotSpot VM的字节码解释器和JIT编译器使用<b>写屏障</b>来维护卡表。写屏障是一小段将卡设置为脏的代码。解释器每次执行更新引用的字节码是，都会执行一段写屏障；JIT编译器在生成更新引用的代码后，也会生成一段写屏障。虽然写屏障使得应用线程增加了一下性能开销，但Minor GC变快了许多，整体的垃圾收集效率也提高了许多，通常应用的吞吐量也会有所改善。</li>
</ul>
<h3 id="垃圾回收算法"><a href="#垃圾回收算法" class="headerlink" title="垃圾回收算法"></a>垃圾回收算法</h3><ul>
<li>标记清除法：首先标记所有需要被回收的对象，在标记完成后统一回收所有被标记的对象。</li>
<li>复制算法：将可用内存按容量划分为大小相等的两块，每次只使用其中的一块。当这一块的内存用完了，就将还存活着的对象复制到另外一块上面，然后再把已使用过的内存空间一次清理掉。</li>
<li>标记整理法：标记过程和标记清除法一样，但是标记完不直接清除，而是让所有存活的对象向一端移动，然后直接清除掉端边界以外的内存。</li>
</ul>
<h3 id="垃圾收集器"><a href="#垃圾收集器" class="headerlink" title="垃圾收集器"></a>垃圾收集器</h3><ul>
<li>Serial收集器(新生代)：单线程收集器，Client模式下默认新生代收集器。</li>
<li>Serial Old收集器(老年代)：使用<b>标记整理算法</b>的单线程收集器，一般用做CMS收集器的后备预案。</li>
<li>ParNew收集器(新生代)：Serial的多线程版本，一般和CMS收集器配合使用。</li>
<li>Parallel Scavenge收集器(新生代)：多线程、高吞吐量的收集器，主要适合后台运算不需要太多交互的任务。</li>
<li>Parallel Old收集器(老年代)：和Parallel Scavenge配合的收集器，使用<b>标记整理算法</b>的并发收集器，在注重吞吐量以及CPU资源敏感的场合，优先考虑二者的组合。</li>
<li>CMS收集器(老年代)：基于<b>标记清除算法</b>的以获取最短回收停顿时间为目标的并发收集器，每个垃圾收集周期只有两次短的停顿，目前应用于大部分B/S系统的服务器端上。</li>
<li>G1收集器(通用)：基于<b>标记整理算法</b>的并发的增量式压缩低停顿的面向服务器端的垃圾收集器，目标是替换CMS。</li>
</ul>
<h2 id="回收方法区"><a href="#回收方法区" class="headerlink" title="回收方法区"></a>回收方法区</h2><p>在永久代，即HotSpot虚拟机的方法区实现中垃圾收集主要回收废弃的常量和无用的类。</p>
<h3 id="废弃的常量"><a href="#废弃的常量" class="headerlink" title="废弃的常量"></a>废弃的常量</h3><p>如果一个字符串“abc”已经进入常量池中，但是当前系统没有任何一个String对象叫做“abc”，即没有任何String对象引用常量池中的“abc”常量，如果这时发生内存回收，且必要的话，这个“abc”常量就会被系统清理出常量池。</p>
<h3 id="无用的类"><a href="#无用的类" class="headerlink" title="无用的类"></a>无用的类</h3><ul>
<li>该类所以的实例都已经被回收，即Java堆中不存在该类的任何实例。</li>
<li>加载该类的ClassLoader已经被回收。</li>
<li>该类对应的java.lang.Class对象没有在任何地方被引用，无法在任何地方通过反射访问该类的方法。</li>
<li>满足上述三个条件的类可以被虚拟机回收，但并不必然被回收。</li>
</ul>
<h2 id="JVM类加载机制"><a href="#JVM类加载机制" class="headerlink" title="JVM类加载机制"></a>JVM类加载机制</h2><p>在Java语言中，类的生命周期包括加载、连接、初始化、使用和卸载五个阶段；其中连接又分为验证、准备和解析三个阶段。而类的加载、连接和初始化都是在程序运行期间(而不是编译期)完成的。</p>
<h3 id="加载"><a href="#加载" class="headerlink" title="加载"></a>加载</h3><ul>
<li>通过一个类的全限定名来获取定义此类的二进制字节流。</li>
<li>将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。</li>
<li>在内存中生成一个代表这个类的java.lang.Class对象，作为方法区这个类的各种数据访问的入口。</li>
<li>数组类本身不通过类加载器创建，而是由Java虚拟机直接创建。</li>
<li>java.lang.Class类的对象并没有明确规定是在Java堆中。对于HotSpot虚拟机而言，Class虽然是对象，但是存放在方法区中。</li>
</ul>
<h3 id="连接"><a href="#连接" class="headerlink" title="连接"></a>连接</h3><ul>
<li>验证：确保Class文件的字节流中包含的信息符合当前虚拟机的要求， 并且不会危害虚拟机自身安全。</li>
<li>准备：正式为类变量分配内存并设置类变量初始值，这些变量所使用的内存都将在方法区中进行分配。</li>
<li>解析：虚拟机将常量池内的符号引用替换为直接引用。<ol>
<li>符号引用：以一组符号来描述所引用的目标，符号可以使任何形式的字面量，只要使用时能无歧义地定位到目标即可。</li>
<li>直接引用：直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。</li>
</ol>
</li>
</ul>
<h3 id="初始化-–-静态变量初始化和执行静态代码块"><a href="#初始化-–-静态变量初始化和执行静态代码块" class="headerlink" title="初始化 – 静态变量初始化和执行静态代码块"></a>初始化 – 静态变量初始化和执行静态代码块</h3><ol>
<li>初始化阶段是执行类构造器clinit()方法的过程。</li>
<li>clinit()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的，编译器收集的顺序是由语句在原文件中出现的顺序所决定的。<b>静态语句块中只能访问定义在静态语句块之前的变量，定义在静态语句块之后的变量，在静态语句块中可以赋值，但是不能访问。</b></li>
<li>clinit()方法和类的构造函数不同，它不需要显式的调用父构造器，虚拟机会保证子类的clinit()方法执行之前，父类的clinit()方法已经执行完毕。</li>
<li>由于父类的clinit()方法先执行，也就意味着父类中定义的静态语句块要优先于子类的类变量赋值操作。</li>
<li>clinit()方法对于类和接口来说并不是必须的，如果一个类中没有静态语句块，也没有对变量的赋值操作，那么编译器可以不为这个类生成clinit()方法。</li>
<li>接口中不能使用静态语句块，但仍然有变量初始化的赋值操作，因此接口与类一样都会生成clinit()方法。但执行接口的clinit()方法并不需要先执行父接口的clinit()方法。只有当父接口中定义的变量被使用时，父接口才会被初始化。同时接口的实现类被初始化时也一样不会执行接口的clinit()方法。</li>
<li>虚拟机会保证一个类的clinit()方法在多线程环境中被正确的加锁、同步，如果多个线程同时去初始化一个类，那么只有一个线程去执行这个类的clinit()方法，其他线程都需要阻塞等待，直到活动线程执行完clinit()方法。如果一个类的clinit()方法中有耗时很长的操作，就可能造成多个进程阻塞。</li>
</ol>
<h3 id="类初始化条件"><a href="#类初始化条件" class="headerlink" title="类初始化条件"></a>类初始化条件</h3><p>虚拟机规范严格规定了有且只有下列五种情况必须立即对类进行初始化（而加载、验证和准备自然而然需要在此之前开始）。</p>
<ol>
<li>遇到new、getstatic、putstatic或invokestatic这四条字节码指令（注意，newarray指令触发的只是数组类型本身的初始化，而不会导致其相关类型的初始化，比如，new String[]只会直接触发String[]类的初始化，也就是触发对类[Ljava.lang.String的初始化，而直接不会触发String类的初始化）时，如果类没有进行过初始化，则需要先对其进行初始化。生成这四条指令的最常见的Java代码场景是：<ul>
<li>使用new关键字实例化对象的时候；</li>
<li>读取或设置一个类的静态字段（被final修饰，放在常量池的常量除外）的时候；</li>
<li>调用一个类的静态方法的时候。</li>
</ul>
</li>
<li>使用java.lang.reflect包的方法对类进行反射调用的时候，如果类没有进行初始化则需要先触发其初始化。</li>
<li>当初始化一个类时，如果发现其父类没有进行过初始化，则需要先触发其父类的初始化。</li>
<li>当虚拟机启动时，用户需要指定一个要执行的主类（包含main方法的那个类），虚拟机会先初始化这个主类。</li>
<li>当使用jdk1.7动态语言支持时，如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄，并且这个方法句柄所对应的类没有进行初始化，则需要先出触发其初始化。</li>
</ol>
<h2 id="类加载器"><a href="#类加载器" class="headerlink" title="类加载器"></a>类加载器</h2><p>虚拟机设计团队把类加载阶段中的<b>通过一个类的全限定名来获取描述此类的二进制字节流</b>这个动作放到了Java虚拟机外部去实现，以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为类加载器。<b>对于任意一个类，都需要由加载它的类加载器和这个类本身一同确认其在Java虚拟机的唯一性。</b></p>
<h3 id="JVM提供的类加载器"><a href="#JVM提供的类加载器" class="headerlink" title="JVM提供的类加载器"></a>JVM提供的类加载器</h3><ul>
<li>启动类加载器(Bootstrap ClassLoader)：又称为引导类加载器，负责将放在JAVA_HOME\lib目录或被-Xbootclasspath参数所指定的路径中的，并被虚拟机所识别(仅按照文件名识别，如rt.jar，名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。</li>
<li>扩展类加载器(Extension ClassLoader)：负责加载JAVA_HOME\lib\ext目录或被java.ext.dirs系统变量所指定的路径中的所有类库，开发者可以直接使用扩展类加载器。</li>
<li>应用程序类加载器(Application ClassLoader)：该类加载器是ClassLoader中的getSystemClassLoader()方法的返回值，所有一般称为<b>系统类加载器</b>。它负责加载用户路径(ClassPath)上所指定的类库，开发者可以直接使用这个类加载器，如果应用程序中没有自定义过自己的类加载器，一般情况下这个就是程序默认的类加载器。</li>
<li>线程上下文类加载器：类java.lang.Thread中的方法getContextClassLoader()和setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过setContextClassLoader(ClassLoader cl)方法进行设置的话，线程将继承其父线程的上下文类加载器。Java应用运行的初始线程的上下文类加载器是系统类加载器。<b>该加载器一般是为了让父类加载器请求子类加载器去完成类加载操作。</b></li>
</ul>
<h3 id="双亲委派模式"><a href="#双亲委派模式" class="headerlink" title="双亲委派模式"></a>双亲委派模式</h3><ul>
<li>一个类加载器收到类加载的请求，它首先不会自己去尝试加载这个类，而是把这个请求委派给父类加载器去完成，每一个层次的加载器都是如此，因此所有的加载请求最终都会传递到顶层的启动类加载器中，只有父类的类加载器反馈自己无法完成这个加载请求（它的搜索范围中没有找到锁需的类）时，子加载器才会尝试自己去加载。<b>这里类加载器之间的父子关系一般都是以组合而不是继承的方式来复用父加载器的代码。</b></li>
<li>该模式的好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如java.lang.Object，它存放在rt.jar之中，无论哪一个类加载器要加载这个类，最终都是委派给处于模型最顶端的启动类加载器来加载，因此Object类在程序的各种类加载器环境中都是同一个类。</li>
</ul>

      
    </div>
      
      <footer>
        
        
  
  <div class="tags">
    <a href="/tags/Java高级特性/">Java高级特性</a>
  </div>

        
  <div class="addthis addthis_toolbox addthis_default_style">
    
      <a class="addthis_button_facebook_like" fb:like:layout="button_count"></a>
    
    
      <a class="addthis_button_tweet"></a>
    
    
      <a class="addthis_button_google_plusone" g:plusone:size="medium"></a>
    
    
      <a class="addthis_button_pinterest_pinit" pi:pinit:layout="horizontal"></a>
    
    <a class="addthis_counter addthis_pill_style"></a>
  </div>
  <script type="text/javascript" src="//s7.addthis.com/js/300/addthis_widget.js"></script>

      
      <div class="clearfix"></div>
      </footer>
  </div>
</article>

<!-- 来必力City版安装代码 -->
<!-- <div id="lv-container" data-id="city" data-uid="MTAyMC8yOTQ5MS82MDU5">
<script type="text/javascript">
   (function(d, s) {
       var j, e = d.getElementsByTagName(s)[0];

       if (typeof LivereTower === 'function') { return; }

       j = d.createElement(s);
       j.src = 'https://cdn-city.livere.com/js/embed.dist.js';
       j.async = true;

       e.parentNode.insertBefore(j, e);
   })(document, 'script');
</script>
<noscript>为正常使用来必力评论功能请激活JavaScript</noscript>
</div> -->
<!-- City版安装代码已完成 -->

</div></div>
    <aside id="sidebar" class="alignright">
  
<div class="widget tagcloud">
  <h3 class="title">文章分类</h3>
  <div class="entry">
    <a href="/tags/JavaWeb/" style="font-size: 17.14px;">JavaWeb</a> <a href="/tags/Java基础/" style="font-size: 15.71px;">Java基础</a> <a href="/tags/Java高级特性/" style="font-size: 15.71px;">Java高级特性</a> <a href="/tags/Python/" style="font-size: 10px;">Python</a> <a href="/tags/Spring基础/" style="font-size: 14.29px;">Spring基础</a> <a href="/tags/WebRTC/" style="font-size: 10px;">WebRTC</a> <a href="/tags/其它/" style="font-size: 20px;">其它</a> <a href="/tags/分布式/" style="font-size: 11.43px;">分布式</a> <a href="/tags/硬件/" style="font-size: 12.86px;">硬件</a> <a href="/tags/计算机基础/" style="font-size: 18.57px;">计算机基础</a> <a href="/tags/设计模式/" style="font-size: 17.14px;">设计模式</a>
  </div>
</div>



    <div class="widget tagcloud">
    <h3 class="title">电子书</h3>
    <ul class="entry">
      <li class='link'><a href='http://www.duokan.com/' target="_blank">多看阅读</a>&nbsp;&nbsp;<a href='https://read.douban.com/' target="_blank">豆瓣阅读</a>&nbsp;&nbsp;<a href='http://e.dangdang.com/' target="_blank">当当阅读</a></li>
      <li class='link'><a href='http://www.ituring.com.cn/' target="_blank">图灵社区</a>&nbsp;&nbsp;<a href='https://www.epubit.com/' target="_blank">异步社区</a>&nbsp;&nbsp;<a href='https://www.geekbang.org/' target="_blank">极客空间</a></li>
    </ul>
  </div>


  
  <div class="widget tag">
    <h3 class="title">友情链接</h3>
      <ul class="entry">
        
          <li class='link'><a href='http://www.cnblogs.com/jietang/' target="_blank">唐洁的博客</a></li>
        
          <li class='link'><a href='http://rednaxelafx.iteye.com/' target="_blank">R大的博客</a></li>
        
          <li class='link'><a href='http://www.hollischuang.com/' target="_blank">阿里大神的博客</a></li>
        
          <li class='link'><a href='http://blog.csdn.net/IT_faquir/' target="_blank">IT_faquir的专栏</a></li>
        
      </ul>
  </div>


  
  <div class="widget tag">
    <h3 class="title">思维导图</h3>
      <ul class="entry">
        
          <li class='link'><a href='https://fengbo4213.github.io/img/开源协议.png' target="_blank">开源协议简介</a></li>
        
          <li class='link'><a href='https://www.processon.com/diagraming/5b0cf757e4b009aef58d4b9d' target="_blank">我的知识体系</a></li>
        
          <li class='link'><a href='https://www.processon.com/view/link/5a2a00e5e4b015e677290b4f' target="_blank">简单的网站架构</a></li>
        
      </ul>
  </div>


  
  <div class="widget tag">
    <h3 class="title">在线手册</h3>
      <ul class="entry">
        
          <li class='link'><a href='http://v3.bootcss.com/' target="_blank">BootStrap官方手册</a></li>
        
          <li class='link'><a href='http://www.iconfont.cn/http://element.eleme.io/#/zh-CN/' target="_blank">阿里巴巴矢量图标库</a></li>
        
      </ul>
  </div>


  
<div class="widget tag">
  <h3 class="title">最新文章</h3>
  <ul class="entry">
    
      <li>
        <a href="/2123/08/30/基础之MySQL原理/">MySQL原理</a>
      </li>
    
      <li>
        <a href="/2024/01/01/其它之ClickHouse索引/">ClickHouse索引</a>
      </li>
    
      <li>
        <a href="/2023/12/17/其它之MySQL线程池/">MySQL线程池</a>
      </li>
    
      <li>
        <a href="/2023/12/16/其它之InnoDB性能优化基础/">InnoDB性能优化基础</a>
      </li>
    
      <li>
        <a href="/2023/12/03/其它之Java8之后的版本新特性/">Java8之后的版本新特性</a>
      </li>
    
  </ul>
</div>

</aside>
    <div class="clearfix"></div>
  </div>
  <footer id="footer" class="inner"><div class="alignleft">
  
  &copy; 2024 fengbo
  
</div>
<div class="clearfix"></div></footer>
  <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>



</body>
</html>

